/*
 * Miccsnookersim, a snooker simulation with AI
 * Copyright (C) 2009 Enno Ruijters, Frans van den Heuvel, Yannick Thimister,
 * Ali B. Ozmen, Esther Verhoef, Achim Leydecker
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.

 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

import java.util.concurrent.atomic.*;
import javax.swing.*;
import java.io.*;

public class Game
{
	private static Player[] players;
	private static Table table;
	private static Simulation gameSimulation;
	private static Display gameDisplay;
	private static Thread runnerThread;
	private static AtomicBoolean playerNoise[];
	private static Settings gameSettings;
	private static PrintWriter output;
	public static Long bigGameLock;
	private static int numShots;
	private static int numCollisions;
	private static int numShotsOfType[];
	private static int numPlayerChanges;
	private static int successfulSafeties;
	private static boolean inSafetyShot;

	static {
		numShotsOfType = new int[7];
	}

	public static Settings getSettings()
	{
		return gameSettings;
	}

	public static void setPlayerNoise(int playerNr, boolean hasNoise)
	{
		playerNoise[playerNr].set(hasNoise);
	}

	public static boolean playerHasNoise(int playerNr)
	{
		return playerNoise[playerNr].get();
	}

	public static Table getTable()
	{
		return table;
	}

	public static Display getDisplay()
	{
		return gameDisplay;
	}

	public static void newGame()
	{
		if (runnerThread != null)
			runnerThread.stop();
		synchronized(bigGameLock) {
			table = new Table(new Vector(285, 100, 0));
			table.getGameLogic().setPlayers(players);
			gameDisplay.setTable(table);
			gameSimulation = new Simulation(table, gameDisplay);
			gameDisplay.drawTable();
			players[0].setTable(table);
			players[1].setTable(table);
		}
	}

	/* Caller should have BGL */
	public static void setPlayer(int playerNr, Player player)
	{
		players[playerNr].terminate();
		if (table.getGameLogic().getActivePlayer() == players[playerNr])
			runnerThread.stop();
		players[playerNr] = player;
		table.getGameLogic().setPlayers(players);
		gameDisplay.drawTable();
	}

	/* Caller should have the BGL */
	public static Player getPlayer(int playerNr)
	{
		return players[playerNr];
	}

	private static void doShot()
	{
		class ShotMaker implements Runnable {
			public void run()
			{
				Player activePlayer;
				int playerNr = -1;
				Cue shot;
				synchronized(bigGameLock) {
					activePlayer = table.getGameLogic().getActivePlayer();
					for (int i = 0; i < players.length; i++)
						if (players[i] == activePlayer)
							playerNr = i;
				}
				shot = activePlayer.getShot();
				synchronized(bigGameLock) {
					Evaluator eval = new Evaluator(table);
					Log.logMessage("Force:");
					Log.logVector(shot.getForce());
					Log.logMessage("Position:");
					Log.logVector(shot.getPosition());
					System.out.format("Simulating on %s (for %s)\n", gameSimulation, table);
					gameSimulation.performShot(shot, true,
					               playerNoise[playerNr],
						       true);
					eval.shotEnd();
					numShots++;
					numCollisions += eval.getNumCollisions();
					numShotsOfType[eval.getShotType()]++;
					if (eval.getShotType() == Evaluator.FOUL
					    || eval.getShotType()
					             == Evaluator.SAFETY_SHOT) {
						if (inSafetyShot)
							successfulSafeties++;
						numPlayerChanges++;
					}

					if (eval.getShotType()
					            == Evaluator.SAFETY_SHOT
					    && !inSafetyShot)
						inSafetyShot = true;
					else
						inSafetyShot = false;
					table.removeListener(eval);
				}
			}
		}

		runnerThread = new Thread(new ShotMaker(), "Simulating thread");
		runnerThread.start();
		try {
			runnerThread.join();
		} catch (Exception e) {
		}
	}

	public static void runGame(int gameNr)
	{
		int numPoints;
		int numBreaks;
		while (!table.getGameLogic().isGameOver()) {
			doShot();
			gameDisplay.drawTable();
		}
		numPoints = table.getGameLogic().getPlayerPoints(true);
		numPoints -= table.getGameLogic().getPlayerPoints(false);
		numBreaks = numPlayerChanges + 1;
		synchronized(bigGameLock) {
			output.format("%d;%d;%f;%d;%f;%f;%d;%d;%d;%d;%d;%d\n",
					gameNr,
					numPoints,
					(float)numPoints / numBreaks,
					numShotsOfType[Evaluator.FOUL],
					(float)numShots / numBreaks,
					(float)numCollisions / numShots,
					successfulSafeties,
					numShotsOfType[Evaluator.DIRECT_SHOT],
					numShotsOfType[Evaluator.INDIRECT_SHOT],
					numShotsOfType[Evaluator.EDGE_SHOT],
					numShotsOfType[Evaluator.COMBINATION_SHOT],
					numShotsOfType[Evaluator.SAFETY_SHOT]);
			numShots = 0;
			numCollisions = 0;
			successfulSafeties = 0;
			numPlayerChanges = 0;
			numShotsOfType = new int[7];
			output.flush();
		}
	}

	public static void exit()
	{
		synchronized(bigGameLock) {
			output.close();
			System.exit(0);
		}
	}

	public static void main(String[] args)
	{
		int gameNr;
		bigGameLock = new Long(0);
		gameSettings = new Settings();
		gameSettings.setSize(400, 200);
		gameSettings.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
		table = new Table(new Vector(285, 100, 0));
		players = new Player[2];
		players[0] = new TacticalAI(4000, table, "Tactical AI (A)");
		players[1] = new MonteCarloAI(2000, table, null, "Monte Carlo AI (B)");
		playerNoise = new AtomicBoolean[2];
		playerNoise[0] = new AtomicBoolean(true);
		playerNoise[1] = new AtomicBoolean(true);
		table.getGameLogic().setPlayers(players);
		gameDisplay = new Display(table);
		gameDisplay.drawTable();
		gameSimulation = new Simulation(table, gameDisplay);
		try {
			output = new PrintWriter(new FileOutputStream("scores.txt",true));
		} catch (FileNotFoundException e) {
			System.err.println("File not found, get a decent OS and try again");
		}
		newGame();
		runGame(0);
		synchronized(bigGameLock) {
			output.close();
		}
	}
}
